組込み現場の「C++」プログラミング 明日から使える徹底入門

高木 信尚(株式会社クローバーフィールド

1.5 アセンブリ言語とのインターフェース

組込み開発では,どうしてもアセンブリ言語で記述せざるをえない状況が出てきます.C++で開発を行う場合でも,その点に関しては同じことがいえます.C++とアセンブリ言語とのインターフェースは,Cとアセンブリ言語とのインターフェースと基本的には同じです.すなわち,インラインアセンブラを用いる方法と,アセンブリ言語で記述したプログラムとリンクする方法です.

1.5.1 asm宣言

C++では,標準規格としてasm宣言がサポートされています.ただし,規格上はあくまでもasm宣言として記述した内容は処理系定義であり,それがインラインアセンブラかどうかさえ規定されていません.また,asm宣言は単一の文字列リテラルを指定することができるだけです.したがって,GCCのインラインアセンブラのような,変数をパラメータとして指定するような,気の利いた仕様はありません.

【標準規格のインラインアセンブラの例】

inline void asm_func()
{
    asm("move r2,r4"); // ← asm宣言 
}

ただし,実際には,処理系の独自拡張として,Cで使うことができるインラインアセンブラの機能はC++でも利用できることでしょう.そして,実際の開発では,標準でサポートしているasm宣言ではなく,Cの場合と同じ,処理系の独自拡張としてのインラインアセンブラ機能を使うことになります.

1.5.2 アセンブリ言語で記述したプログラムとのリンク

アセンブリ言語で記述したプログラムとリンクさせる場合,コンパイラにアセンブリ言語のソースコードを出力させてみて,コーリングコンベンション*7などを調べることが多いと思います.コーリングコンベンションに関するドキュメントが入手できる場合であっても,実際のコンパイル結果を調べることは有意義です.しかし,C++コンパイラが出力したアセンブリ言語のソースコードは,Cの常識からすると,ビックリするような量であったり,意味不明なシンボルが並んでいたりします.

たとえば,Cの関数名は,アセンブリ言語になったとしても,せいぜい前にアンダースコアなどの記号が付く程度でした.しかし,C++の場合には,引数の型情報が埋め込まれたり,名前空間に関する情報が埋め込まれることもあって,非常に煩雑なシンボルに展開されます.また,クラスのメンバー関数の場合,通常の関数とはコーリングコンベンションが異なります.

こういった事情から,C++を使う場合であっても,アセンブリ言語で記述したプログラムとリンクする場合には,C結合を用いてC用の関数としてアセンブリ言語で記述することがほとんどです.

【C++側の記述】

// C++側の記述
extern "C" int asm_func(int arg);

【アセンブリ側の記述】

// アセンブリ側の記述
.globl _asm_func
_asm_func:
         …
        rts

どうしてもC++結合の関数をアセンブリ言語で記述する場合には,何もしない空の関数をいったんC++で記述し,それをコンパイルしてできたアセンブリ言語のソースに修正を加えるほうが現実的でしょう.

*7 関数名がどんなシンボルに展開され,引数をどのように渡して,返却値をどのように受け取るかといった,関数の呼び出しに関するルールのことです.

1.5.3 スタートアップの記述

アセンブリ言語で記述すべきプログラムとして,最も重要なものの1つがスタートアップ*8です.C++を使う場合でも,やはりスタートアップは必要です.

C++用のスタートアップは,基本的にはC用のスタートアップと変わりません.1つだけ異なるのは,動的初期化処理と終了処理を行う必要がある部分です(図1.2参照).

●図1.2 スタートアップの処理内容

図1.2 スタートアップの処理内容

「1.3.3 「const修飾子」はかなり異なる」でも触れましたが,C++の場合,静的なオブジェクトであっても,初期化子に定数式以外を指定することができます(関数の呼び出しなどを含んでいてもかまいません).このような動的初期化を解決するための処理をスタートアップで行う必要があるのです.また,静的なオブジェクトがクラス型であった場合,動的初期化と対になるものとして,プログラム終了時の後始末を行う必要があります(第2章で解説しますが,デストラクタといいます).

動的初期化処理や終了処理の記述方法は,処理系によって異なります.詳しくは,処理系のドキュメントを参照してください.

ところで,スタートアップは必ずしもアセンブリ言語で書かなければならないわけではありません.Cのスタートアップもそうですが,スタックポインタの初期化など,本当に必要な部分だけをアセンブリ言語で記述して,残りはCで記述することもできます.

ここで注意しなければならないことは,Cの場合,main関数を呼び出すコードをCで記述することができましたが,C++ではmain関数を直接呼び出すことも,main関数へのポインタを取得することもできません.その意味で,C++はスタートアップを書くには不向きです.もちろん,プログラム開始位置をmain関数以外にすれば済むわけですが,原則として,スタートアップの記述にはC++は使うべきではありません.

*8 CPUにリセットがかかってからmain関数を呼び出すまでの処理のことです.main関数から戻った後の処理も記述する必要があります.